﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using DBManager;
using System.Globalization;
using System.Collections;
using System.IO;
using System.Collections.Concurrent;
//using System.Web.Script.Serialization;

namespace TCPServer
{


    public class TCPServerManager
    {
        static ConcurrentQueue<byte[]> bufferQueue = new ConcurrentQueue<byte[]>();

        private TcpListener tcpListener;
        private Thread listenThread;
        private Thread bufferManager;
        private  DateTime lastTime = DateTime.Now.AddDays(-2) ;

        public  void CheckOlderLog()
        {
           List<GPSTracker> list = DBManager.DBManager.Instance.GetAllTracker();
           //int n = list.Count;
           foreach (GPSTracker item in list)
           {
               List<GPSLog> logList = DBManager.DBManager.Instance.GetLatestOneDay(item);
               if (logList.Count > 0)
               {
                   GPSLog begin = logList.First();
                   GPSLog end = logList.Last();
                   long distance = (long)end.journey - (long)begin.journey;

                   if (distance < 0)
                       distance += 4294967295;

                   item.totalMile += (int)distance/1000;
               }
           }

           DBManager.DBManager.Instance.DeleteOlderLog();

           DBManager.DBManager.Instance.Save();

           lastTime = DateTime.Now;

        }

        public TCPServerManager()
        {
            this.tcpListener = new TcpListener(IPAddress.Any, 3000);
            this.listenThread = new Thread(new ThreadStart(ListenForClients));
            this.bufferManager = new Thread(new ThreadStart(ProcessBuffer));
            this.bufferManager.IsBackground = true;

            this.bufferManager.Start();
            this.listenThread.Start();
            //timer = new Timer(CheckOlderLog, null, 0, 86400000);
        }




        private void ProcessBuffer()
        {
            byte[] toEnq = new byte[1024];

            while (true)
            {
                if (bufferQueue.Count > 0)
                {
                    if (bufferQueue.TryDequeue(out toEnq))
                    {
                        //Buffer.BlockCopy(message, 0, toEnq, 0, 1024);

                        GPSLog log = parseBuffer(toEnq);
                        if (log == null)
                            continue;

                        //GPSTracker tracker = DBManager.DBManager.Instance.GetTracker((int)log.gpstrakerId);

                        //if (tracker == null)
                        //    continue;



                        // GPSLog last_log = DBManager.DBManager.Instance.GetLatestLog((int)log.gpstrakerId);

                        //if (last_log == null ||
                        //    log.inputStatus == true )
                        {
                            GeoLocation geo = DBManager.DBManager.Instance.FindGeo((decimal)log.Latitiude, (decimal)log.Longitude);

                            if (geo == null)
                                RetreiveLocation(log);
                            else
                            {
                                log.province = geo.province;
                                log.city = geo.city;
                                log.district = geo.district;
                            }
                        }
                        //else if (last_log != null && last_log.inputStatus == false && log.inputStatus == false)
                        //{
                        //    log.district = last_log.district;
                        //    log.city = last_log.city;
                        //    log.province = last_log.province;
                        //}

                        DBManager.DBManager.Instance.Add(log);
                        DBManager.DBManager.Instance.Save();

                    }

                    //Thread.Sleep(1000);
                }
                else
                {
                    if ((DateTime.Now - lastTime).TotalDays >= 1)
                        CheckOlderLog();

                    Thread.Sleep(500);
                }
               
            }
        }

        private static void RetreiveLocation(GPSLog log)
        {
            string url = "http://maps.googleapis.com/maps/api/geocode/json?latlng=" + log.Latitiude.ToString() + "," + log.Longitude.ToString() + "&sensor=false&language=TH";
            System.Text.Encoding encoder = Encoding.GetEncoding(874);

            WebRequest request = WebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "application/json";

            using (StreamWriter writer = new StreamWriter(request.GetRequestStream(),encoder))
            {
                writer.Write("{id : 'test'}");
            }

            WebResponse response = request.GetResponse();
            Stream stream = response.GetResponseStream();
            string json = "";

            using (StreamReader reader = new StreamReader(stream, encoder))
            {
                while (!reader.EndOfStream)
                {
                    json += reader.ReadLine();
                }

                string[] jsonToken = json.Split(new char[11] { ',', '\\', '{', '}', '[', ']', '"', ':', '\n', ' ', '\t' });
                int district_index = 0;
                int isFound = 0;

                for (int i = 0; i < jsonToken.Length; ++i)
                {
                    if (jsonToken[i] == "long_name")
                    {
                        district_index = i + 5;
                        isFound++;
                        if (isFound == 2)
                        break;
                    }
                }


                if ( district_index < jsonToken.Length && jsonToken[district_index] != null )
                     log.district = jsonToken[district_index].ToString();
                if ( district_index + 103 < jsonToken.Length && jsonToken[district_index + 103] != null )
                    log.city = jsonToken[district_index + 103].ToString();
                if (district_index + 206 < jsonToken.Length && jsonToken[district_index + 206] != null)
                    log.province = jsonToken[district_index + 206];

                if (log.district != "")
                {
                    GeoLocation new_geo = new GeoLocation();
                    new_geo.city = log.city;
                    new_geo.district = log.district;
                    new_geo.province = log.province;
                    new_geo.lat = (decimal)log.Latitiude;
                    new_geo.@long = (decimal)log.Longitude;

                    DBManager.DBManager.Instance.Add(new_geo);
                    DBManager.DBManager.Instance.Save();
                }

            }

           

           // string a = jsonToken[0];
           
        }

        private void ListenForClients()
        {
            this.tcpListener.Start();

            while (true)
            {
                //blocks until a client has connected to the server
                TcpClient client = this.tcpListener.AcceptTcpClient();

                //create a thread to handle communication 
                //with connected client
                Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
                clientThread.Start(client);
            }
        }

        public GPSLog parseBuffer(byte[] buffer)
        {
            GPSLog gpsLog = null;
            int index = 0;
            //Header '$$'
            if (buffer[index] != '$' || buffer[index + 1] != '$')
            {
                return null;
            }
            //Length
            index += 2;

            byte[] b_length = new byte[4];
            b_length[2] = buffer[index];
            b_length[3] = buffer[index+1];

            if (BitConverter.IsLittleEndian)
                Array.Reverse(b_length);

            int length = BitConverter.ToInt32(b_length, 0);

            index += 2;

            //Track ID 7 bytes;
            string trackerId = "";
            for (int i = 0; i < 7; ++i)
            {
                Byte currentByte = buffer[index + i];
                string s = currentByte.ToString("X").PadLeft(2, '0');

                if (s[0] == 'F')
                    break;

                trackerId += s[0];

                if (s[1] == 'F')
                    break;
                trackerId += s[1];
              
            }

            index += 7;

            //Command
            if (buffer[index] != 0x99 || buffer[index + 1] != 0x55)
                return null;

            index += 2;

            //Data
            ASCIIEncoding encoder = new ASCIIEncoding();


            string data = encoder.GetString(buffer, index, length- index-4);

            string[] gpsLogToken = data.Split(new char[2] { ',', '|' });

            int trackerbuffer_id = Convert.ToInt32(trackerId);


            GPSTracker tracker = DBManager.DBManager.Instance.GetTracker(trackerbuffer_id);

            if (tracker == null)
                return null;

           

            if (tracker.GPSModel.model_name == "VT300")
            {
                gpsLog = parseVT300(gpsLog, trackerId, gpsLogToken);
            }
            else if (tracker.GPSModel.model_name == "VT310")
            {
                gpsLog = parseVT310(gpsLog, trackerId, gpsLogToken);
            }
            else if (tracker.GPSModel.model_name == "VT400")
            {
                gpsLog = parseVT400(gpsLog, trackerId, gpsLogToken);
            }

            gpsLog.gpstrakerId = tracker.trackerId;



            return gpsLog;
        }

        private static GPSLog parseVT400(GPSLog gpsLog, string trackerId, string[] gpsLogToken)
        {
            gpsLog = new GPSLog();
            gpsLog.gpstrakerId = Convert.ToInt32(trackerId);

            //DateTime
            string dateTimeStr = gpsLogToken[8] + gpsLogToken[0].Substring(0, 6);

            DateTime currentTime = DateTime.Now.AddHours(-7);

            gpsLog.time = currentTime;

            //GPSstatus
            bool gpsStatus = false;

            if (gpsLogToken[1][0] == 'A')
                gpsStatus = true;

            gpsLog.gpsStatusIndicator = gpsStatus;

            //Latitude

            decimal latitude = 0;

            string latitudeStr = gpsLogToken[2];
            latitude += Convert.ToDecimal(latitudeStr.Substring(0, 2));
            latitude += (Convert.ToDecimal(latitudeStr.Substring(2))) / 60;

            //N|S
            if (gpsLogToken[3][0] == 'S')
                latitude = -latitude;
            gpsLog.Latitiude = latitude;

            //Longtitude 

            decimal longitude = 0;

            string longitudeStr = gpsLogToken[4];
            longitude += Convert.ToDecimal(longitudeStr.Substring(0, 3));
            longitude += (Convert.ToDecimal(longitudeStr.Substring(3))) / 60;

            if (gpsLogToken[5][0] == 'W')
                longitude = -longitude;
            gpsLog.Longitude = longitude;

            gpsLog.Speed = Convert.ToDecimal(gpsLogToken[6]);

            parseHeading(gpsLog, gpsLogToken);

            if (gpsLogToken[gpsLogToken.Length - 12][1] == '0')
            {
                gpsLog.inputStatus = false;
            }
            else
            {
                gpsLog.inputStatus = true;
            }

            string energerVol = gpsLogToken[gpsLogToken.Length - 10];

            int numbericVol = Convert.ToInt32(energerVol, 16);

            decimal percentEng = (((numbericVol * 12) / (decimal)1024) / (decimal)24) * 100;

            gpsLog.energyPercent = percentEng;

            string jorneyValue = gpsLogToken[gpsLogToken.Length - 1];

            long journey = Convert.ToInt64(jorneyValue.Trim(new char[1] {  '\0' }), 16);

            gpsLog.journey = journey;

            return gpsLog;
        }

        private static GPSLog parseVT310(GPSLog gpsLog, string trackerId, string[] gpsLogToken)
        {
            gpsLog = new GPSLog();
            gpsLog.gpstrakerId = Convert.ToInt32(trackerId);

            //DateTime
            string dateTimeStr = gpsLogToken[8] + gpsLogToken[0].Substring(0, 6);

            DateTime currentTime = DateTime.Now.AddHours(-7);

            gpsLog.time = currentTime;

            //GPSstatus
            bool gpsStatus = false;

            if (gpsLogToken[1][0] == 'A')
                gpsStatus = true;

            gpsLog.gpsStatusIndicator = gpsStatus;

            //Latitude

            decimal latitude = 0;

            string latitudeStr = gpsLogToken[2];
            latitude += Convert.ToDecimal(latitudeStr.Substring(0, 2));
            latitude += (Convert.ToDecimal(latitudeStr.Substring(2))) / 60;

            //N|S
            if (gpsLogToken[3][0] == 'S')
                latitude = -latitude;
            gpsLog.Latitiude = latitude;

            //Longtitude 

            decimal longitude = 0;

            string longitudeStr = gpsLogToken[4];
            longitude += Convert.ToDecimal(longitudeStr.Substring(0, 3));
            longitude += (Convert.ToDecimal(longitudeStr.Substring(3))) / 60;

            if (gpsLogToken[5][0] == 'W')
                longitude = -longitude;
            gpsLog.Longitude = longitude;

            gpsLog.Speed = Convert.ToDecimal(gpsLogToken[6]);

            parseHeading(gpsLog, gpsLogToken);

            if (gpsLogToken[gpsLogToken.Length - 6][0] == '0')
            {
                gpsLog.inputStatus = false;
                gpsLog.availStatus = false;
            }
            else if (gpsLogToken[gpsLogToken.Length - 6][0] == '1')
            {
                gpsLog.inputStatus = false;
                gpsLog.availStatus = true;
            }
            else if (gpsLogToken[gpsLogToken.Length - 6][0] == '2')
            {
                gpsLog.inputStatus = true;
                gpsLog.availStatus = false;
            }
            else if (gpsLogToken[gpsLogToken.Length - 6][0] == '3')
            {
                gpsLog.inputStatus = true;
                gpsLog.availStatus = true;
            }

            string energerVol = gpsLogToken[gpsLogToken.Length - 5];

            int numbericVol = Convert.ToInt32(energerVol, 16);

            decimal percentEng = (((numbericVol * 6) / (decimal)1024)/(decimal)6) * 100;

            gpsLog.energyPercent = percentEng;

            string jorneyValue = gpsLogToken[gpsLogToken.Length - 1];

            long journey = Convert.ToInt64(jorneyValue.Trim(new char[1] { '\0' }), 16);

            gpsLog.journey = journey;

            return gpsLog;
        }

        private static GPSLog parseVT300(GPSLog gpsLog, string trackerId, string[] gpsLogToken)
        {
            gpsLog = new GPSLog();
            gpsLog.gpstrakerId = Convert.ToInt32(trackerId);

            //DateTime
            string dateTimeStr = gpsLogToken[8] + gpsLogToken[0].Substring(0, 6);

            DateTime currentTime = DateTime.Now.AddHours(-7);

            gpsLog.time = currentTime;

            //GPSstatus
            bool gpsStatus = false;

            if (gpsLogToken[1][0] == 'A')
                gpsStatus = true;

            gpsLog.gpsStatusIndicator = gpsStatus;

            //Latitude

            decimal latitude = 0;

            string latitudeStr = gpsLogToken[2];
            latitude += Convert.ToDecimal(latitudeStr.Substring(0, 2));
            latitude += (Convert.ToDecimal(latitudeStr.Substring(2))) / 60;

            //N|S
            if (gpsLogToken[3][0] == 'S')
                latitude = -latitude;
            gpsLog.Latitiude = latitude;

            //Longtitude 

            decimal longitude = 0;

            string longitudeStr = gpsLogToken[4];
            longitude += Convert.ToDecimal(longitudeStr.Substring(0, 3));
            longitude += (Convert.ToDecimal(longitudeStr.Substring(3))) / 60;

            if (gpsLogToken[5][0] == 'W')
                longitude = -longitude;
            gpsLog.Longitude = longitude;

            gpsLog.Speed = Convert.ToDecimal(gpsLogToken[6]);

            parseHeading(gpsLog, gpsLogToken);
            bool inputStatus = false;

            if (gpsLogToken[gpsLogToken.Length - 6][1] == '1')
                inputStatus = true;
            gpsLog.inputStatus = inputStatus;

            string jorneyValue = gpsLogToken[gpsLogToken.Length - 1];

            long journey = Convert.ToInt64(jorneyValue.Trim(new char[1] { '\0' }), 16);

            gpsLog.journey = journey;

            return gpsLog;
        }

        private static void parseHeading(GPSLog gpsLog, string[] gpsLogToken)
        {
            decimal degree = Convert.ToDecimal(gpsLogToken[7]);
            string heading = "";
            if (degree >= (decimal)337.5 && degree <= 360 || degree >= 0 && degree < (decimal)22.5)
            {
                heading = "N";
            }
            else if (degree >= (decimal)22.5 && degree < (decimal)67.5)
            {
                heading = "NE";
            }
            else if (degree >= (decimal)67.5 && degree < (decimal)112.5)
            {
                heading = "E";
            }
            else if (degree >= (decimal)112.5 && degree < (decimal)157.5)
            {
                heading = "SE";
            }
            else if (degree >= (decimal)157.5 && degree < (decimal)202.5)
            {
                heading = "S";
            }
            else if (degree >= (decimal)202.5 && degree < (decimal)247.5)
            {
                heading = "SW";
            }
            else if (degree >= (decimal)247.5 && degree < (decimal)292.5)
            {
                heading = "W";
            }
            else if (degree >= (decimal)292.5 && degree < (decimal)337.5)
            {
                heading = "NW";
            }


            gpsLog.heading = heading;
        }

        private void HandleClientComm(object client)
        {
            TcpClient tcpClient = (TcpClient)client;
            NetworkStream clientStream = tcpClient.GetStream();

            byte[] message = new byte[1024];
            int bytesRead;

            while (true)
            {
                bytesRead = 0;

                try
                {
                    //blocks until a client sends a message
                    bytesRead = clientStream.Read(message, 0, 1024);
                }
                catch
                {
                    //a socket error has occured
                    break;
                }

                if (bytesRead == 0)
                {
                    //the client has disconnected from the server
                    break;
                }


                byte[] toEnq = new byte[1024];

                Buffer.BlockCopy(message, 0, toEnq, 0, 1024);

                bufferQueue.Enqueue(toEnq);

            }

            tcpClient.Close();
        }

    }
}
